package com.acargil.sudoku.model.algorithm.possibility;

import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import com.acargil.sudoku.model.structure.Coordinate;
import com.acargil.sudoku.model.structure.StandardCell;
import com.acargil.sudoku.model.structure.StandardGrid;

public class StandardPossibilityManager implements Serializable{

	private static final long serialVersionUID = 1L;

	private StandardGrid grid;
	private int[][][] possibilityMatrix;
	private StandardCellPossibility[][] cellPosibilities;
	
	public StandardPossibilityManager(StandardGrid grid) {
		this.grid = grid;
		this.possibilityMatrix = 
			new int[StandardGrid.COLUMN_NUMBER][StandardGrid.ROW_NUMBER][StandardCellPossibility.MAX_NUMBER_OF_POSSIBLE_VALUE];
		this.cellPosibilities = 
			new StandardCellPossibility[StandardGrid.COLUMN_NUMBER][StandardGrid.ROW_NUMBER];
	}

	public int[][][] getPossibilitiesOfAllCells() {
		int[][] cells;
		Map<Coordinate, PossibilityChange> possibiltyChangeMap;
		Set<Coordinate> changedCoordinates;
		
		cells = grid.getCells();
		possibiltyChangeMap = getPossibilityChangeMap(cells);
		changedCoordinates = possibiltyChangeMap.keySet();
		for (Coordinate coordinate : changedCoordinates) {
			recalculatePossibility(coordinate, possibiltyChangeMap.get(coordinate));
		}
		
		for (int x = 0; x < StandardGrid.COLUMN_NUMBER; x++) {
			for (int y = 0; y < StandardGrid.ROW_NUMBER; y++) {
				possibilityMatrix[x][y] = cellPosibilities[x][y].getPossibilityArray();
			}
		}
		
		return possibilityMatrix;
	}
	
	private void recalculatePossibility(Coordinate coordinate, PossibilityChange possibilityChange) {
		int x = coordinate.getX();
		int y = coordinate.getY();
		int xFactor = x / StandardGrid.SQUARE_SIZE;
		int yFactor = y / StandardGrid.SQUARE_SIZE;
		int possibilityChangeValue = possibilityChange.getValue();
		
		if (possibilityChange.isAdd()) {
			// vertical & horizontal
			for (int index = 0; index < StandardGrid.ROW_NUMBER; index++) {
				if (index != y) {
					cellPosibilities[x][index].addPossibility(possibilityChangeValue);
				}
				if (index != x) {
					cellPosibilities[index][y].addPossibility(possibilityChangeValue);
				}
			}
			
			// square
			for (int ix = 0; ix < StandardGrid.SQUARE_SIZE; ix++) {
				for (int iy = 0; iy < StandardGrid.SQUARE_SIZE; iy++) {
					cellPosibilities[ix+xFactor][iy+yFactor].addPossibility(possibilityChangeValue);
				}
			}
		}
		else {
			// vertical & horizontal
			for (int index = 0; index < StandardGrid.ROW_NUMBER; index++) {
				if (index != y) {
					cellPosibilities[x][index].removePossibility(possibilityChangeValue);
				}
				if (index != x) {
					cellPosibilities[index][y].removePossibility(possibilityChangeValue);
				}					
			}
			
			// square
			for (int ix = 0; ix < StandardGrid.SQUARE_SIZE; ix++) {
				for (int iy = 0; iy < StandardGrid.SQUARE_SIZE; iy++) {
					cellPosibilities[ix+xFactor][iy+yFactor].removePossibility(possibilityChangeValue);
				}
			}
		}			
	}

	private Map<Coordinate, PossibilityChange> getPossibilityChangeMap(int[][] cells) {
		Map<Coordinate, PossibilityChange> possibiltyChangeMap = new HashMap<Coordinate, PossibilityChange>();
		int currentCellValue;
		
		for (int x = 0; x < StandardGrid.COLUMN_NUMBER; x++) {
			for (int y = 0; y < StandardGrid.ROW_NUMBER; y++) {
				currentCellValue = cells[x][y];
				if (cellPosibilities[x][y].isValueChanged(currentCellValue)) {	// if value changed, calculate possibility matrix
					if (currentCellValue == StandardCell.EMPTY_VALUE) {	
						possibiltyChangeMap.put(new Coordinate(x, y), new PossibilityChange(true, currentCellValue));
					}
					else {	
						possibiltyChangeMap.put(new Coordinate(x, y), new PossibilityChange(false, currentCellValue));
					} // cell is not empty
				} // if cell value changed
			}
		}
		
		return possibiltyChangeMap;
	}
	
	
	private class PossibilityChange {
		
		private final boolean isAdd;
		private final int value;
		
		private PossibilityChange(boolean isAdd, int value) {
			this.isAdd = isAdd;
			this.value = value;
		}
		
		public boolean isAdd() {
			return isAdd;
		}

		public int getValue() {
			return value;
		}
	}
}
